import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../../util/constants';
import { RolesType } from '../decorator/my.decorator';
import { UserRolesPermsVo } from '../../sys/admin/user/vo/user-roles-perms.vo';
import { RoleEnum } from '../../util/enum/role.enum';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<RolesType>(
      ROLES_KEY,
      [context.getHandler(), context.getClass()],
    );
    // 如果没有@HasRoles(),就不需要权限
    if (!requiredRoles) {
      return true;
    }
    const user: UserRolesPermsVo = context.switchToHttp().getRequest()['user'];
    // 如果用户是ROOT,不需要权限
    if (user.roles.includes(RoleEnum.Root)) {
      return true;
    }
    const can = this.checkRoles(requiredRoles, user.roles);
    if (!can) {
      throw new UnauthorizedException('需要角色' + requiredRoles.toString());
    }
    return can;

    // 参考 https://www.npmjs.com/package/express-jwt-permissions
    // 'read' Required: "read"
    // ['read', 'write']  Required: "read" AND "write"
    //  [ ['admin'], ['read', 'write']]  Required: "admin" OR ("read" AND "write")
  }

  /**
   * 验证权限
   * @param requiredRoles 需要的权限
   * @param userRoles 用户拥有的权限
   * @private
   */
  private checkRoles(requiredRoles: RolesType, userRoles: string[]): boolean {
    // 比如 'read'
    if (typeof requiredRoles === 'string') {
      return userRoles.includes(requiredRoles);
    } else if (Array.isArray(requiredRoles) && requiredRoles.length > 0) {
      // 比如 ['read', 'write']
      if (typeof requiredRoles[0] === 'string') {
        return (requiredRoles as string[]).every((role: string) =>
          userRoles.includes(role),
        );
      } else {
        // 比如 [ ['admin'], ['read', 'write']]
        return (requiredRoles as string[][]).some((role: string[]) =>
          this.checkRoles(role, userRoles),
        );
      }
    } else {
      return false;
    }
  }
}
